﻿using Dorado.Extensions;
using Dorado.Platform.FileSystems.WebSite;
using Dorado.Platform.Plugins;
using Dorado.Platform.Themes;
using NuGet;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Versioning;
using NuGetPackageBuilder = NuGet.PackageBuilder;

namespace Dorado.Platform.Packaging
{
    public class PackageBuilder : IPackageBuilder
    {
        private readonly IWebSiteFolder _webSiteFolder;

        public PackageBuilder(IWebSiteFolder webSiteFolder)
        {
            this._webSiteFolder = webSiteFolder;
        }

        private static readonly string[] _ignoredThemeExtensions = new[] {
            "obj", "pdb", "exclude"
        };

        private static readonly string[] _ignoredThemePaths = new[] {
            "/obj/"
        };

        private static bool IgnoreFile(string filePath)
        {
            return String.IsNullOrEmpty(filePath) ||
                _ignoredThemePaths.Any(filePath.Contains) ||
                _ignoredThemeExtensions.Contains(Path.GetExtension(filePath).NullEmpty() ?? "");
        }

        public Stream BuildPackage(PluginDescriptor pluginDescriptor)
        {
            return BuildPackage(pluginDescriptor.ConvertToExtensionDescriptor());
        }

        public Stream BuildPackage(ThemeManifest themeManifest)
        {
            return BuildPackage(themeManifest.ConvertToExtensionDescriptor());
        }

        private Stream BuildPackage(ExtensionDescriptor extensionDescriptor)
        {
            var context = new BuildContext();
            BeginPackage(context);
            try
            {
                EstablishPaths(context, _webSiteFolder, extensionDescriptor.Id, extensionDescriptor.ExtensionType);
                SetCoreProperties(context, extensionDescriptor);
                EmbedFiles(context);
            }
            finally
            {
                EndPackage(context);
            }

            if (context.Stream.CanSeek)
            {
                context.Stream.Seek(0, SeekOrigin.Begin);
            }

            return context.Stream;
        }

        private static void BeginPackage(BuildContext context)
        {
            context.Stream = new MemoryStream();
            context.Builder = new NuGetPackageBuilder();
        }

        private static void EndPackage(BuildContext context)
        {
            context.Builder.Save(context.Stream);
        }

        private static void SetCoreProperties(BuildContext context, ExtensionDescriptor extensionDescriptor)
        {
            context.Builder.Id = PackagingUtils.BuildPackageId(extensionDescriptor.Id, extensionDescriptor.ExtensionType);
            context.Builder.Version = new SemanticVersion(extensionDescriptor.Version);
            context.Builder.Title = extensionDescriptor.Name ?? extensionDescriptor.Id;
            context.Builder.Description = extensionDescriptor.Description.NullEmpty() ?? "No Description";
            context.Builder.Authors.Add(extensionDescriptor.Author ?? "Unknown");

            if (Uri.IsWellFormedUriString(extensionDescriptor.WebSite, UriKind.Absolute))
            {
                context.Builder.ProjectUrl = new Uri(extensionDescriptor.WebSite);
            }
        }

        private static void EstablishPaths(BuildContext context, IWebSiteFolder webSiteFolder, string extensionName, string extensionType = "Plugin")
        {
            context.SourceFolder = webSiteFolder;
            if (extensionType.IsCaseInsensitiveEqual("theme"))
            {
                context.SourcePath = "~/Themes/" + extensionName + "/";
                context.TargetPath = "\\Content\\Themes\\" + extensionName + "\\";
            }
            else
            {
                context.SourcePath = "~/Plugins/" + extensionName + "/";
                context.TargetPath = "\\Content\\Plugins\\" + extensionName + "\\";
            }
        }

        private static void EmbedFiles(BuildContext context)
        {
            var basePath = context.SourcePath;
            foreach (var virtualPath in context.SourceFolder.ListFiles(context.SourcePath, true))
            {
                // skip ignores files
                if (IgnoreFile(virtualPath))
                {
                    continue;
                }
                // full virtual path given but we need the relative path so it can be put into
                // the package that way (the package itself is the logical base path).
                // Get it by stripping the basePath off including the slash.
                var relativePath = virtualPath.Replace(basePath, "");
                EmbedVirtualFile(context, relativePath);
            }
        }

        private static void EmbedVirtualFile(BuildContext context, string relativePath)
        {
            var file = new VirtualPackageFile(
                context.SourceFolder,
                context.SourcePath + relativePath,
                context.TargetPath + relativePath);
            context.Builder.Files.Add(file);
        }

        #region Nested type: BuildContext

        private class BuildContext
        {
            public Stream Stream { get; set; }
            public NuGetPackageBuilder Builder { get; set; }

            public IWebSiteFolder SourceFolder { get; set; }
            public string SourcePath { get; set; }
            public string TargetPath { get; set; }
        }

        #endregion Nested type: BuildContext

        #region Nested type: VirtualPackageFile

        private class VirtualPackageFile : IPackageFile
        {
            private readonly IWebSiteFolder _webSiteFolder;
            private readonly string _virtualPath;
            private readonly string _packagePath;

            public VirtualPackageFile(IWebSiteFolder webSiteFolder, string virtualPath, string packagePath)
            {
                _webSiteFolder = webSiteFolder;
                _virtualPath = virtualPath;
                _packagePath = packagePath;
            }

            [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Supposed to return an open stream.")]
            public Stream GetStream()
            {
                var stream = new MemoryStream();
                _webSiteFolder.CopyFileTo(_virtualPath, stream);
                stream.Seek(0, SeekOrigin.Begin);
                return stream;
            }

            public string Path
            {
                get { return _packagePath; }
            }

            public string EffectivePath
            {
                get { return _packagePath; }
            }

            public FrameworkName TargetFramework
            {
                get { return null; }
            }

            public IEnumerable<FrameworkName> SupportedFrameworks
            {
                get { return Enumerable.Empty<FrameworkName>(); }
            }
        }

        #endregion Nested type: VirtualPackageFile
    }
}